import java.net.Socket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.io.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Scanner;
import java.io.File;
import java.util.ArrayList;
import java.util.Vector;
import trigest.*;

public class saperePeer{
	public Peer me;
	//public InetAddress server;
	//public int port;
    Socket peerSocket = null;
    PrintWriter out = null;
    BufferedReader in = null;
    boolean signIn = false; //lets us know if this user has signed in
    ArrayList<aFile> searchResult;
    ArrayList<aFile> fileList;//list of Files available to search on the server
    ArrayList<Peer> peerList;//list of peers connected to this super peer
    ArrayList<Peer> spList;//List of all superPeers
    Vector<String>          results;
    MessageDigest md;
    boolean dist = false;
    boolean listening=true;
    Boot parent = new Boot(); //lets the peer know who its parent is in the distributed approach
	
	//default constructor used for testing with the local host
    public saperePeer() throws UnknownHostException
	{
		parent.server = InetAddress.getByName("127.0.0.1");
		parent.port = 4441;
		me = new Peer();
		searchResult = new ArrayList();
		fileList = new ArrayList();
		peerList = new ArrayList();
		spList = new ArrayList();
		results = new Vector();
	}
	
	//add a list to the list of files available to search on this super peer 
	public synchronized void addFile(aFile f)
	{
		fileList.add(f);
	}
	
	//add peer to list on peers connected to this super peer
	public synchronized void addPeer(Peer p)
	{
		peerList.add(p);
	}
	
	//add a searchResult to the list
	public synchronized void addSearchResult(aFile f)
	{
		searchResult.add(f);
	}
	
	//default constructor
	public saperePeer(String s, int p) throws UnknownHostException
	{
		parent.server = InetAddress.getByName(s);
		parent.port = p;
		me = new Peer();
		searchResult = new ArrayList();
		fileList = new ArrayList();
		peerList = new ArrayList();
		spList = new ArrayList();
	}
	
	//try to connect to the boot/super peer server
	public void connect() throws UnknownHostException
	{
		try {
			me.myConnection = new Socket(parent.server,parent.port);
			out = new PrintWriter(me.myConnection.getOutputStream(), true);
			in = new BufferedReader(new InputStreamReader(me.myConnection.getInputStream()));
			//out.println("connect");
			
		}
		catch (UnknownHostException e) {
            System.err.println("Don't know about host: " + parent.server);
            System.exit(1);
        } catch (IOException e) {
            System.err.println("Couldn't get I/O for the connection to: " + parent.server);
            System.exit(1);
        }
		
	}
	
	//Registration Method for GUI 
	public void register(User regMe) throws UnknownHostException
	{
		//Send registration packet
		
		try{
			//regMe.currentIP = InetAddress.getLocalHost();
			regMe.currentIP = InetAddress.getByName("127.0.0.1");
			md = MessageDigest.getInstance("SHA-1");
			md.update(regMe.pass.getBytes());
			regMe.setHash(md.digest());
			regMe.pass="";
		}
		catch (UnknownHostException e)
		{
			System.out.println("Could not get Local Host Address");
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		System.out.println(regMe.toString()+byteToHex(regMe.getHash()));
		out.println(regMe.toString()+byteToHex(regMe.getHash()));
	}
	
	//regiester used for the command line
	public void register() throws UnknownHostException
	{
		//Send registration packet
		//first need to generate the Registration Packet
		Scanner in = new Scanner(System.in);
		User regMe = new User();
		System.out.println("Name:");
		regMe.name = in.next();
		System.out.println("Email:");
		regMe.email = in.next();
		System.out.println("User Name:");
		regMe.userID = in.next();
		System.out.println("Password:");
		//regMe.setHash(in.next().getBytes());
		regMe.pass=in.next();
		
		try{
			//regMe.currentIP = InetAddress.getLocalHost();
			regMe.currentIP = InetAddress.getByName("127.0.0.1");
			md = MessageDigest.getInstance("SHA-1");
			md.update(regMe.pass.getBytes());
			regMe.setHash(md.digest());
			regMe.pass="";
		}
		catch (UnknownHostException e)
		{
			System.out.println("Could not get Local Host Address");
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		System.out.println(regMe.toString()+byteToHex(regMe.getHash()));
		out.println(regMe.toString()+byteToHex(regMe.getHash()));
	}
	//function used to login in to the server
	public void login(String uname, String pass) throws IOException
	{
		
		try {
			md = MessageDigest.getInstance("SHA-1");
			md.update(pass.getBytes());
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		out.println("login;" + uname + ";" + byteToHex(md.digest()));
		String login = in.readLine();
		if(!login.equals("Login Fail"))
		{
			signIn=true;
		}
		System.out.println(login);
		
	}
	//function used to log of the server
	public void logoff() throws IOException
	{
		//send log of packet
		out.println("Quit");
		me.myConnection.close();
		out.close();
		in.close();
	}
	//function to advertise all the files available in the /share dir
	public void advertise()
	{
		//Send advertise packet
		//need to add code to read the files in the default directory and convert them
		//into aFile objects
		aFile newF = new aFile();
		String curDir = System.getProperty("user.dir");
		System.out.println(curDir+"/share");
		File dir = new File(curDir+"/share");
		String[] child = dir.list();
		for(int i=0;i<child.length;i++)
		{
			if(!child[i].startsWith(".")){
				System.out.println(child[i]);
				newF.name = child[i];
				try{
					newF.Peer = InetAddress.getLocalHost().getHostAddress();
					newF.setHash(new trigest(child[i]).getSignature());
					//System.out.println(this.byteToHex(newF.getHash()));
				}
				catch (UnknownHostException e)
				{
					System.out.println("Error:"+e);
				}
				catch(Exception e)
				{
					System.out.println("Error:"+e);
				}
				newF.size = child[i].length();
				newF.shortAbstract = " ";
				//System.out.println(newF+byteToHex(newF.getHash()));
				//searchResult.add(newF); //used for testing
				this.addFile(newF); //add this to our own personal fileList for distributed
				//if this is not a superPeer then we need to advertise
				if(!this.me.superPeer)
					out.println(newF+byteToHex(newF.getHash()));
			}
		}
	}
	public synchronized Vector<String> search(String search)
	{
		results.clear();
		//Send search packet
		searchResult.clear(); //clear the search result list before we use it
	    Search mySearch = new Search(search);
		trigest searchText = null;
		try{
			searchText = new trigest(mySearch.sText);
			//System.out.printf("im here");
			mySearch.searchHash = searchText.getSignature();
			//System.out.println(byteToHex(searchText.getSignature()));
		}
		catch (Exception e)
		{
			System.out.println(" ---Error---");
		}
		//System.out.println(mySearch.toString()+byteToHex(mySearch.searchHash));
		//if we are not a superPeer but just a peer we need to send the file to be distrbiuted
		if(this.dist)
			out.println("peerToSuper:"+mySearch.toString()+byteToHex(mySearch.searchHash));
		else
			out.println(mySearch.toString()+byteToHex(mySearch.searchHash));
		//if this is a distributed system need to send out search to all of the superPeers and Boot
		//wait for response back from boot or superPeer
		String inputLine;
		Scanner s = null;
		String cType = "";
		try {
			while ((inputLine = in.readLine()) != null) {
				// System.out.println(inputLine);
				s = new Scanner(inputLine).useDelimiter("\\s*;\\s*");
				while (s.hasNext()) {
					cType = s.next();
					if (cType.equals("class:aFile")) {
						aFile newF = new aFile();
						newF.name = s.next();
						newF.size = Integer.parseInt(s.next());
						newF.Peer = s.next();
						newF.shortAbstract = s.next();
						newF.setHash(hexToByte(s.next()));
						// add these reults to the searchResult list of the peer
						addSearchResult(newF);
					}
				}
				if(cType.equals("done"))
				{
					break;
				}
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//print out search results for testing
		//before we print out we need to sort using the current document ranking
		//ADD IN DOCUMENT RANKING
		for(int i =0;i<searchResult.size();i++)
		{
			System.out.println((i+1)+":"+searchResult.get(i).toString());
			results.add((String) searchResult.get(i).toString());
		}
		return results;
	}
	
	//Function to send search request to the boot server or super peer
	public synchronized void search()
	{
		//Send search packet
		searchResult.clear(); //clear the search result list before we use it
		Scanner inA = new Scanner(System.in);
		System.out.println("Search:");
		Search mySearch = new Search(inA.nextLine());
		trigest searchText = null;
		try{
			searchText = new trigest(mySearch.sText);
			//System.out.printf("im here");
			mySearch.searchHash = searchText.getSignature();
			//System.out.println(byteToHex(searchText.getSignature()));
		}
		catch (Exception e)
		{
			System.out.println(" ---Error---");
		}
		//System.out.println(mySearch.toString()+byteToHex(mySearch.searchHash));
		//if we are not a superPeer but just a peer we need to send the file to be distrbiuted
		if(this.dist)
			out.println("peerToSuper:"+mySearch.toString()+byteToHex(mySearch.searchHash));
		else
			out.println(mySearch.toString()+byteToHex(mySearch.searchHash));
		//if this is a distributed system need to send out search to all of the superPeers and Boot
		//wait for response back from boot or superPeer
		String inputLine;
		Scanner s = null;
		String cType = "";
		try {
			while ((inputLine = in.readLine()) != null) {
				// System.out.println(inputLine);
				s = new Scanner(inputLine).useDelimiter("\\s*;\\s*");
				while (s.hasNext()) {
					cType = s.next();
					if (cType.equals("class:aFile")) {
						aFile newF = new aFile();
						newF.name = s.next();
						newF.size = Integer.parseInt(s.next());
						newF.Peer = s.next();
						newF.shortAbstract = s.next();
						newF.setHash(hexToByte(s.next()));
						// add these reults to the searchResult list of the peer
						addSearchResult(newF);
					}
				}
				if(cType.equals("done"))
				{
					break;
				}
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//print out search results for testing
		//before we print out we need to sort using the current document ranking
		//ADD IN DOCUMENT RANKING
		for(int i =0;i<searchResult.size();i++)
			System.out.println((i+1)+":"+searchResult.get(i).toString());
	}
	
	//if this a search taking place through a superPeer or on a superPeer
	public synchronized void searchHere(Search s, boolean ourSearch)
	{
		if(s==null)//then this is a search executed by this superPeer so need to fill obj first
		{
			Scanner in = new Scanner(System.in);
			System.out.println("Search:");
			s = new Search(in.nextLine());
			trigest searchText = null;
			try{
				searchText = new trigest(s.sText);
				//System.out.printf("im here");
				s.searchHash = searchText.getSignature();
				//System.out.println(byteToHex(searchText.getSignature()));
			}
			catch (Exception e)
			{
				System.out.println(" ---Error---");
			}
		}
		//now we have a search object to send out to the rest of the superPeers for searching
		//first lets make sure we have an updated spList
		out.println("spList");//ask boot for updated list
		String inputLine;
	    Scanner scan = null;
	    String cType = "";    
	    try {
			while (!(inputLine = in.readLine()).equals(";done;")) {
				//System.out.println(inputLine);
				scan = new Scanner(inputLine).useDelimiter("\\s*;\\s*");
				while(scan.hasNext())
				{
					cType=scan.next();
					if(cType.equals("class:Peer"))
					{
						Peer p = new Peer();
						p.ipAddress = InetAddress.getByName(scan.next().substring(1));
						if(scan.next().equals("true"))
							p.superPeer = true;
						else
							p.superPeer = false;
						p.throughput = scan.nextInt();
						p.numConnections = scan.nextInt();
						spList.add(p);
					}
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		//now we need to start a search thread for each one of the superPeers
		//if this is our search we store the results in searchResult, else send 
		//searchResult list back to peer that requested it
		
		for(int i=0;i<spList.size();i++)
		{
			//start a thread for each superPeer make sure to
			//let them add results to the resultList
			//need to wait until these threads complete until we can go back
			searchThread a = new searchThread(this,s,spList.get(i));
		}
		
		
		
	}
	
	public synchronized String searchFileSpace(Search s) {
		// this is where the search will take place
		ArrayList<aFile> resultArray = new ArrayList();
		// aFile[] resultArray = new aFile[fileList.size()];
		String result = "";
		int matchFlag = 0;
		for (int i = 0; i < fileList.size(); i++) {
			matchFlag = SearchAsst.sfcompare(s.searchHash, fileList.get(i)
											 .getHash());
			if (matchFlag > 0) {
				docRank.Rank(fileList.get(i), matchFlag);
				resultArray.add(fileList.get(i));
			}
		}
		for (int i = 0; i < resultArray.size(); i++)
			result += resultArray.get(i).toString()
			+ byteToHex(resultArray.get(i).getHash()) + "\n";
		System.out.println(result);
		return result;
	}
	
	static String byteToHex(byte[] msg) {
		String result = "";
		for (int i = 0; i < msg.length; i++) {
			char highBits, LowBits;
			highBits = (char) ((msg[i] >>> 4) & 0xf);
			LowBits = (char) (msg[i] & 0xf);
			highBits = (char) ((highBits > 9) ? 'A' + (highBits - 10)
							   : '0' + highBits);
			LowBits = (char) ((LowBits > 9) ? 'A' + (LowBits - 10)
							  : '0' + LowBits);
			result += highBits;
			result += LowBits;
		}
		return result;
	}
	
	public static byte[] hexToByte(String s) {
	    int len = s.length();
	    byte[] data = new byte[len / 2];
	    for (int i = 0; i < len; i += 2) {
	        data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
								  + Character.digit(s.charAt(i+1), 16));
	    }
	    return data;
	}
	
	//will download the specified file from its owner
	public void download(aFile f)
	{
		//Send down load request packet
		PrintWriter dout = null;
	    BufferedReader din = null;
	    BufferedWriter fout = null;
	    Socket dCon=null;
	    String of="";
	    String curDir = System.getProperty("user.dir");
		try {
			dCon = new Socket(f.Peer,4442);
			dout = new PrintWriter(dCon.getOutputStream(), true);
	        din = new BufferedReader(new InputStreamReader(dCon.getInputStream()));
	        fout = new BufferedWriter(new FileWriter(curDir+"/download/"+f.name));
	        dout.println("download:"+f.toString());
	        while(!(of=din.readLine()).equals(";done;"))
	        	fout.write(of+System.getProperty("line.separator"));
	        dout.println("Quit"); //send the quit msg
	        fout.close();
			dout.close();
			din.close();
			dCon.close();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	
	//Function that will switch this peer to distributed mode
	public void distributed(saperePeer me)
	{
		this.dist=true;
		out.println("distributed");
		String inputLine, cType;
		Scanner s;
		//if we are not a superPeer we need to get a new parent
		if(!this.me.superPeer)
		{
			try {
				inputLine = in.readLine();
				System.out.println(inputLine);
				s = new Scanner(inputLine).useDelimiter("\\s*;\\s*");
				while(s.hasNext())
				{
					cType=s.next();
					if(cType.equals("class:Boot"))
					{
						this.parent.server = InetAddress.getByName(s.next());
						this.parent.port = Integer.parseInt(s.next());
						//now we need to change are main partner to the superPeer info we just got
						this.logoff(); //logoff the current server
						this.connect(); //connect to the new server
					}
					else
						System.out.println(cType);
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
	}
	
	//cahnge this peer to a supePeer
	public void superPeer(saperePeer me)
	{
		this.me.superPeer=true;
		out.println("superPeer");
		//make sure we are set to distributed since we are a superPeer
		this.distributed(me);
		//once we turn into a superPeer go ahead and call advertise so we can see what
		//files we have on our own system
		this.advertise();
	}
	
	//command line testing function
	public static void main(String args[])
	{
		saperePeer me = null;
		String un,pass;
		try{
			//me = new saperePeer("152.14.243.27",4444);
			me = new saperePeer();
			new pThread(me).start(); //start the download server
		}
		catch (UnknownHostException e)
		{
			System.out.println("Unknown Host:"+e);
		}
		Scanner in = new Scanner(System.in);
		int option=0;
		while(me.listening)
		{
			System.out.println("-Sapere Peer Client-");
			System.out.println("1:Connect");
			System.out.println("2:Login");
			System.out.println("3:Logoff");
			System.out.println("4:Register");
			System.out.println("5:Advertise");
			System.out.println("6:Search");
			System.out.println("7:Download");
			System.out.println("8:Distributed:"+me.dist);
			System.out.println("9:SuperPeer");
			option = in.nextInt();
			switch(option){
				case 1:
					System.out.println("Boot Server IP:");
					
					try{
						me.parent.server = InetAddress.getByName(in.next());
						System.out.println("Establishing Connecion...");
					me.connect();}
					catch (IOException e)
				{
					System.out.println("IOException:"+e);
				}
					
					break;
				case 2:
					System.out.println("User Name:");
					un = in.next();
					System.out.println("Password:");
					pass = in.next();
					try{
					me.login(un, pass);}
					catch (IOException e)
				{
					System.out.println("Login in failed:"+e);
				}
					break;
				case 3:
					try{
						me.logoff();
						me.listening=false;
						System.exit(1);	
					}
					catch (IOException e){
					System.out.println("Logoff failed:"+e);}
				break;
			case 4:
				try{
				me.register();}
				catch (UnknownHostException e)
				{
					System.out.println("Could not get Local Host Address");
				}
				break;
			case 5:
				me.advertise();
				break;
			case 6:
				if(me.me.superPeer)
					me.searchHere(null, true);
				else
					me.search();
				break;
			case 7:
				System.out.print("#:");
				me.download(me.searchResult.get(in.nextInt()-1));
				break;
			case 8:
				me.distributed(me);
				break;
			case 9:
				me.superPeer(me);
				break;
			default:
				break;
			}
			
		}
		
		
	}
}